############################################################
#
# Authentication package for EP
#
# Time-stamp: <05/08/11 15:49:17 ostolop>
#
############################################################
package EP::Session;

use strict;

use DBI;
use Crypt::PasswdMD5;

use EP::Config;
use EP::Common;

######################################################################
# called when we want to restart session (e.g. when logging out)
######################################################################
sub end_session {
  my ($session_id) = @_;

  # remove log file
  unlink ( "$EP::Config::h->{LOG}/session_log.".$session_id.".xml" ) or warn "Couldn't remove session log! $!";

  # expire the session
  if ( &expire_session( $session_id ) == 0 ) {
      print "Unable to expire current session\n";
      return 0;
  }

  undef $session_id;
}

sub valid_user {
  my ($username, $password) = @_;

  if ( $username eq '' || $password eq '' ) {
    print "Please fill in a username and password" and return 0;
  }

  my ($crypt_password, $salt) = &check_user($username);

  if ( $crypt_password eq &unix_md5_crypt($password, $salt) ) {
    return 1;
  } else {
    print "Invalid username or password" and return 0;
  }
}

sub check_user {
  my ($username) = @_;

  #retrieve the encrypted password
  my $sth = $EP::Config::h->{DBH}->prepare( "select password, salt from users where username = ?" );
  $sth->execute( $username );

  my ( $crypt_password, $salt) = $sth->fetchrow_array();
  $sth->finish;

  return ($crypt_password, $salt);
}

sub register_default_user_folder {
  my ($username) = @_;

  my $data = { folder_name => $username,
	       location    => $username,
	       username    => $username };

  return EP::Common::DB::insert_folder ( $data );
}

sub insert_user {
  my ($salt, $username, $password, $password_chk, $first_name, $last_name, $institute, $department, $address, $telephone, $email) = @_;

  if ( $first_name eq '' || $last_name eq '' || $email eq '' || $username eq '' || $password eq '' ) {
      print "Some required fields are missing" and return 0;
  } elsif ( $email !~ /\@.+/ ) {
      print "Invalid email-address" and return 0;
  } elsif ( &check_user($username) ne '' ) {
    throw EP::Common::Error ( -value => UM_USERNAME_ALREADY_TAKEN, -err_args => [$username] );
  } elsif ( $password ne $password_chk ) {
      print "Passwords do not match" and return 0;
  }

  #insert the user
  my $sth = $EP::Config::h->{DBH}->prepare( "insert into users (username,password,salt,first_name,last_name,institute,department,address,telephone,email) values(?,?,?,?,?,?,?,?,?,?)" );
  $sth->execute($username, &unix_md5_crypt($password, $salt), $salt, $first_name, $last_name, $institute, $department, $address, $telephone, $email);

  return if !$sth;

  $sth->finish;

  return 1;
}

sub delete_user {
  my ($username) = @_;

  my $sth = $EP::Config::h->{DBH}->prepare( "DELETE FROM users WHERE username=?" );
  $sth->execute($username);

  return $DBI::errstr if !$sth;
  $sth->finish;

  return 1;
}

sub create_sessionid {
  my ($username, $hr) = @_;
  my ($session_id);

  #random number generation
  my @chars = ( 'a' .. 'z', 'A' .. 'Z', '0' .. '9' );

  foreach ( 1 .. 20 ) {
    $session_id .= $chars[ int rand( $#chars ) ];
  }

  my $cur_time = time();
  my $expire = $cur_time + ( $hr * 60 * 60 );

  #insert the created sessionid into the database
  my $sth = $EP::Config::h->{DBH}->prepare( "insert into sessions_metadata (session_id, username, create_date, expire) values (?,?,?,?)" );
  $sth->execute($session_id,$username,$cur_time, $expire);

  return $session_id;
}

sub update_session_on_user_login {
  my ($session_id, $username, $expire_hrs) = @_;

  my $cur_time = time();
  my $expire = $cur_time + ( $expire_hrs * 60 * 60 );

  # try to update the session in the database
  my $sth = $EP::Config::h->{DBH}->prepare ( "UPDATE sessions_metadata SET username=?, create_date=?, expire=? WHERE session_id=?" );
  my $rc = $sth->execute ( $username, $cur_time, $expire, $session_id );

  # if no rows were affected, this is a new session - create a whole record in the metadata then
  if ( $rc == 0 ) {
    $sth = $EP::Config::h->{DBH}->prepare ( "INSERT INTO sessions_metadata (session_id, username, create_date, expire) VALUES (?,?,?,?)" );
    $rc = $sth->execute($session_id, $username, $cur_time, $expire);
  }

  return "Couldn't insert/update session metadata on user login: " . $DBI::errstr if !$rc;
  return 1;
}

######################################################################
# retrieves session metadata (e.g. username, expiry, etc) from the DB
# based on the session id and sets %session keys accordingly
######################################################################
sub set_session_metadata {
  my ($session) = @_;

  my $session_metadata = &EP::Common::DB::get_session_metadata ( $session->{_session_id} );

  # in Oracle field names are in upper case (?). Need to convert to lower.
  foreach ( keys %{$session_metadata} ) {
    $session->{$_} = $session_metadata->{$_};
  }

  if ( $session->{username} ) {
    warn "Metadata was set up (username: $session->{username})" if $EP::Config::h->{DEBUG};
    return;
  }

  warn "Not much metadata to set up, no username returned" if DEBUG > 1;
}

######################################################################
# sets a field in the session metadata table
######################################################################
sub update_session_metadata {
  my ( $session_id, $metadata_key, $metadata_value ) = @_;
  return if not defined $session_id;

  my $sth = $EP::Config::h->{DBH}->prepare ( "UPDATE sessions_metadata SET query_id=? WHERE session_id=?" );
  my $rc = $sth->execute ( $metadata_value, $session_id );

  if ( $rc == 0 ) {
    $sth = $EP::Config::h->{DBH}->prepare ( "INSERT INTO sessions_metadata (session_id, query_id) VALUES (?,?)" );
    $rc = $sth->execute ( $session_id, $metadata_value );
  }
}

######################################################################
# returns 1 if the session has expired, and if it hasn't, sets its
# expiration at $EP::Config::h->{EXPIRE_SESSION} hours into the future and
# returns 0 (i.e. session is still valid)
# NB: if unable to extend expiration, will expire the session!
######################################################################
sub is_expired {
  my ($session_id) = @_;
  return 0 if not defined $session_id;

  #check whether the session has expired
  my $session_metadata = &EP::Common::DB::get_session_metadata ( $session_id );
  return 0 if not $session_metadata->{expire}; # expire not yet set

  #session has expired
  return 1 if time() >= $session_metadata->{expire};

  #increase the expiration time
  my $new_expire = time() + ( $EP::Config::h->{EXPIRE_SESSION} * 60 * 60 );
  return 0 if ( &EP::Common::DB::set_session_expire ( $session_id, $new_expire ) );

  warn "Wasn't able to extend session expiration!" if $EP::Config::h->{DEBUG};
  return 1;
}

sub expire_session {
  my ($session_id) = @_;

  #set the expiration time in the session table to current time
  my $expire = time();

  my $sth = $EP::Config::h->{DBH}->prepare( "update sessions_metadata set expire = ? where session_id = ? " );
  my $rc = $sth->execute( $expire, $session_id);

  warn $DBI::errstr and return 0 if !$rc;

  return 1;
}

1;
